import gitlab
import requests
from typing import (
    Optional,
    Union,
    List
)
from gitlab.v4.objects.projects import GroupProject, Project
from gitlab.v4.objects.files import ProjectFile
from gitlab.v4.objects.commits import ProjectCommit
from invoke import context


class Git:
    """
    https://docs.gitlab.com/ee/api/commits.html
    https://python-gitlab.readthedocs.io/en/stable/gl_objects/projects.html#reference
    """
    cmd = context.Context()

    def __init__(self, url: str,
                 private_token: Optional[str] = None,
                 oauth_token: Optional[str] = None,
                 job_token: Optional[str] = None,
                 ssl_verify: Union[bool, str] = True,
                 http_username: Optional[str] = None,
                 http_password: Optional[str] = None,
                 timeout: Optional[float] = None,
                 api_version: str = "4",
                 session: Optional[requests.Session] = None,
                 per_page: Optional[int] = None,
                 pagination: Optional[str] = None,
                 order_by: Optional[str] = None, ):
        self.api = gitlab.Gitlab(url=url, private_token=private_token,
                                 oauth_token=oauth_token, job_token=job_token,
                                 ssl_verify=ssl_verify, http_username=http_username,
                                 http_password=http_password, timeout=timeout,
                                 api_version=api_version, session=session, per_page=per_page,
                                 pagination=pagination, order_by=order_by
                                 )

    def list_projects(self, search=None, as_list=False, per_page=10, page=1, order_by="last_activity_at",
                      group=None, **kwargs) -> Union[List[GroupProject], List[Project]]:
        """

        Attribute	Type	Required	Description

        per_page
        page
        archived	boolean	 No	Limit by archived status.
        id_after	integer	 No	Limit results to projects with IDs greater than the specified ID.
        id_before	integer	 No	Limit results to projects with IDs less than the specified ID.
        last_activity_after	datetime	 No	Limit results to projects with last_activity after specified time. Format: ISO 8601 (YYYY-MM-DDTHH:MM:SSZ)
        last_activity_before	datetime	 No	Limit results to projects with last_activity before specified time. Format: ISO 8601 (YYYY-MM-DDTHH:MM:SSZ)
        membership	boolean	 No	Limit by projects that the current user is a member of.
        min_access_level	integer	 No	Limit by current user minimal access level.
        order_by	string	 No	Return projects ordered by id, name, path, created_at, updated_at, last_activity_at, or similarity fields. repository_size, storage_size, packages_size or wiki_size fields are only allowed for admins. similarity (introduced in GitLab 14.1) is only available when searching and is limited to projects that the current user is a member of. Default is created_at.
        owned	boolean	 No	Limit by projects explicitly owned by the current user.
        repository_checksum_failed 	boolean	 No	Limit projects where the repository checksum calculation has failed (Introduced in GitLab 11.2).
        repository_storage	string	 No	Limit results to projects stored on repository_storage. (admins only)
        search_namespaces	boolean	 No	Include ancestor namespaces when matching search criteria. Default is false.
        search	string	 No	Return list of projects matching the search criteria.
        simple	boolean	 No	Return only limited fields for each project. This is a no-op without authentication as then only simple fields are returned.
        sort	string	 No	Return projects sorted in asc or desc order. Default is desc.
        starred	boolean	 No	Limit by projects starred by the current user.
        statistics	boolean	 No	Include project statistics. Only available to Reporter or higher level role members.
        topic	string	 No	Comma-separated topic names. Limit results to projects that match all of given topics. See topics attribute.
        visibility	string	 No	Limit by visibility public, internal, or private.
        wiki_checksum_failed 	boolean	 No	Limit projects where the wiki checksum calculation has failed (Introduced in GitLab 11.2).
        with_custom_attributes	boolean	 No	Include custom attributes in response. (admins only)
        with_issues_enabled	boolean	 No	Limit by enabled issues feature.
        with_merge_requests_enabled	boolean	 No	Limit by enabled merge requests feature.
        with_programming_language	string	 No	Limit by projects which use the given programming language.
                """
        if group:
            return self.api.groups.get(group).projects.list(search=search, as_list=as_list, per_page=per_page,
                                                            page=page,
                                                            order_by=order_by, **kwargs)
        else:
            return self.api.projects.list(search=search, as_list=as_list, per_page=per_page,
                                          order_by=order_by, **kwargs)

    def list_commits(self, project_id, ref_name, **kwargs) -> List[ProjectCommit]:
        """
        Attribute	Type	Required	Description

        id	integer/string	yes	The ID or URL-encoded path of the project owned by the authenticated user
        ref_name	string	no	The name of a repository branch, tag or revision range, or if not given the default branch
        since	string	no	Only commits after or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ
        until	string	no	Only commits before or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ
        path	string	no	The file path
        all	boolean	no	Retrieve every commit from the repository
        with_stats	boolean	no	Stats about each commit are added to the response
        first_parent	boolean	no	Follow only the first parent commit upon seeing a merge commit
        order	string	no	List commits in order. Possible values: default, topo. Defaults to default, the commits are shown in reverse chronological order.
        trailers	boolean	no	Parse and include Git trailers for every commit
        :return:
        """
        return self.api.projects.get(id=project_id).commits.list(ref_name=ref_name, **kwargs)

    def get_file(self, project_id, **kwargs) -> ProjectFile:
        """

        Parameters:

        file_path (required) - URL encoded full path to new file. Ex. lib%2Fclass%2Erb
        ref (required) - The name of branch, tag or commit
        """
        return self.api.projects.get(id=project_id).files.get(**kwargs)

    @classmethod
    def clone(cls, address, dirname=".", ref="master"):
        """克隆"""
        with cls.cmd.cd(dirname):
            cls.cmd.run(f"git clone -b {ref} {address}")
